/*
 * ATENÇÃO: Recomendo a não acessar no barramento modbus não muita variáveis por vez por causa do erro do boudrate
 */

#include "app.h"
#if (LOG_MODBUS_SLAVE == pdON) || (LOG_MODBUS_MASTER == pdON)
#include "stdio_uc.h"
#endif

#include "uart1_irq.h"
#define uartM_Init			uart1_Init
#define uartM_EmptyTx 		uart1_EmptyTx
#define uartM_GetChar 		uart1_GetChar
#define uartM_BufferQtdRx 	uart1_BufferQtdRx
#define uartM_ClearBufferRx uart1_ClearBufferRx
#define uartM_WriteTx		uart1_WriteTx

#include "uart3_irq.h"
#define uartS_Init			uart3_Init
#define uartS_EmptyTx 		uart3_EmptyTx
#define uartS_GetChar 		uart3_GetChar
#define uartS_BufferQtdRx 	uart3_BufferQtdRx
#define uartS_ClearBufferRx uart3_ClearBufferRx
#define uartS_WriteTx		uart3_WriteTx

static int slave_SetValue(uint addr, u16 value);
static int slave_PutString(u8* buffer, u16 count);
static int slave_GetValue(uint addr, u16* value);
static int slave_ReadRegisters(uint addrInit, u8* query, uint count);
static int slave_WriteRegister(uint addr, u16 value);
static int slave_WriteRegisters(uint addrInit, u8* query, uint count);

static int master_PutString(u8* buffer, u16 count);

void modbus_Init(uint id) {
	// SLAVE
	modbus_SlaveInit(id, slave_PutString, uartS_GetChar, uartS_BufferQtdRx, uartS_ClearBufferRx);
	modbus_SlaveAppendFunctions(slave_ReadRegisters, slave_WriteRegister, slave_WriteRegisters);
	rs485_ENTXS_DIR |= rs485_ENTXS;
	rs485_ENTXS_OFF = rs485_ENTXS;
	uartS_Init(BAUDRATE_RASPBERRY);

	// MASTER
	modbus_MasterInit(master_PutString, uartM_GetChar, uartM_ClearBufferRx);
	modbus_MasterTimeout(3000);
	rs485_ENTXM_DIR |= rs485_ENTXM;
	rs485_ENTXM_OFF = rs485_ENTXM;
	uartM_Init(BAUDRATE_THERMOMETER);
}

// ##################################################################################################
// SLAVE
// ##################################################################################################
static int slave_PutString (u8* buffer, u16 count) {
	rs485_ENTXS_ON = rs485_ENTXS;
	int ret = uartS_WriteTx(buffer, count);
	while (!uartS_EmptyTx()); // Vamos esperar que o serial envie a mensagem
	rs485_ENTXS_OFF = rs485_ENTXS;
	return ret;
}

// -------------------------------------------------------------------------------------------------------------------
// Descrição: 	Lê registradores
// Parametros:	addr: Endereço do registrador
//				value: Ponteiro para o retorno do valor de 16bits
// Retorna:		0: Se a leitura foi feita com suscesso
//				-1: O endereço do registrador não existe (modbusILLEGAL_DATA_ADDRESS (02))
// -------------------------------------------------------------------------------------------------------------------
static int slave_GetValue(uint addr, u16* value) {
	int ret = 0;
	uint addrTherm = 0;

	if ( (addr >= 0x400) && (addr < 0x1000) ) {
		addrTherm = (uint)((addr-0x400)/5); 		// Encontrar o indice do termômetro no vetor de acordo com o addr
		addr = (uint)((addr-0x400)%5)+0x400; 		// Conveter para o endereço base para acesso
		#if (LOG_MODBUS_SLAVE == pdON)
    	plognp("    [addrTherm=0x%x] [addrbase=0x%x]"CMD_TERMINATOR, addrTherm, addr);
    	#endif
	}

	switch (addr) {

	// ---------------------------------------------------------------------------------------------------------------------------------------
	// ID
	case 0:	*value = (MODEL[1] << 8) | MODEL[0]; break;
	case 1:	*value = (MODEL[3] << 8) | MODEL[2]; break;
	case 2:	*value = (FIRMWARE_VERSION[2] << 8) | FIRMWARE_VERSION[0]; break;

	// ---------------------------------------------------------------------------------------------------------------------------------------
	// REGISTRADORES DE TRABALHO
	case 0x10: *value = nTHERMOMETER; break;

	// Ler se há alguma ação em andamento, seja configuração de grandeza de temperatura; reset estatiticas ou o experimento
	case 0x11: *value = control.action; break;

	// ---------------------------------------------------------------------------------------------------------------------------------------
	// REGISTRADORES DOS TEMÔMETROS
	case 0x400: *value = control.thermometer[addrTherm].stsCom; break;
	case 0x401: *value = control.thermometer[addrTherm].sts; break;
	case 0x402: *value = control.thermometer[addrTherm].value; break;
	case 0x403: *value = control.thermometer[addrTherm].valueMin; break;
	case 0x404: *value = control.thermometer[addrTherm].valueMax; break;

	// ---------------------------------------------------------------------------------------------------------------------------------------
	// retorna sinalizando acesso em um registrador inválido
	default: ret = -1; break;
	}

	return ret;
}

// -------------------------------------------------------------------------------------------------------------------
// Descrição: 	Escrever nos registradores
// Parametros:	addr: Endereço do registrador
//				value: valor de 16bits para ser gravado no registrador
// Retorna:		0: Se a gravação foi feita com suscesso
//				-1: O endereço do registrador não existe (modbusILLEGAL_DATA_ADDRESS (2))
// 				-2: Dado a ser gravado é inválido (modbusILLEGAL_DATA_VALUE (3))
//				-3: No momento o dispositivo não pode gravar o valor, ou executar a tarefa (modbusSLAVE_DEVICE_FAILURE (4))
// -------------------------------------------------------------------------------------------------------------------
static int slave_SetValue(uint addr, u16 value) {
	int ret = 0;

	switch (addr) {

	// ---------------------------------------------------------------------------------------------------------------------------------------
	// REGISTRADORES DO SISTEMA

	// Especifica em que grandeza de temperatura os termômetros devem ser configurado
	//		0 = Tratamento da temperatura determinada pelo jumper do termômetro
	// 		1 = Configurar todos os termômetros a trabalhar com graus Celsius(0)
	//		2 = Configurar todos os termômetros a trabalhar com graus Fahrenheit(1)
	case 0x14:
		//if (control.action) return -3; // RERTIRADO PORQUE O PESSOAL DA UFSC NÃO ESTÁ SABENDO COMO TRATAR AS EXCEÇÕES NO SBC
		if (!control.action) temp_SetType(value);
		break;

	// Reinicia as estatísticas de temperatura
	case 0x17:
		if (control.action) return -3; // se o recurso de hardware está fazendo uma ação com os termomentros não vamos executar o novo comando
		temp_InitStatistic();
		break;

	// ---------------------------------------------------------------------------------------------------------------------------------------
	// retorna sinalizando acesso em um registrador inválido
	default:
		ret = -1;
	}

	return ret;
}

// -------------------------------------------------------------------------------------------------------------------
// Descrição: 	Função complementar do modbus slave para ler os registradores
// Parametros:	Ver exemplos no modbus_Slave função modbus_SlaveAppendFunctions
// Retorna:		A função deve retornar
//					modbusNO_ERROR: Leitura feito com sucesso
//					modbusILLEGAL_DATA_ADDRESS:	Endereço do registrador inexistente
//					modbusILLEGAL_DATA_VALUE:	Valor inválido
//					modbusSLAVE_DEVICE_FAILURE:	Um irrecuperável erro ocorreu enquanto o dispositivo estava tentando
//													executar a ação solicitada.
// -------------------------------------------------------------------------------------------------------------------
static int slave_ReadRegisters(uint addrInit, u8* query, uint count) {
	// checa se o endereço está dentro dos limite
	if ((count == 0) || (count > 120) ) return modbusILLEGAL_DATA_ADDRESS;

	// preenche o buffer modbus com os valores dos registradores
	int ret;
	u16 value;
	uint i; for (i=0;i<count;i++) {
		ret = slave_GetValue(addrInit+i, &value);
		if (ret == -1) return modbusILLEGAL_DATA_ADDRESS;
		else if (ret == -2) return modbusILLEGAL_DATA_VALUE;

		query[2*i] = (u8)(value>>8);
		query[2*i+1] = (u8)(value&0xFF);

		#if (LOG_MODBUS_SLAVE == pdON)
    	plognp("MODBUS: [i=%d %d] [a=0x%x] [msb=0x%x %c] [lsb=0x%x %c]"CMD_TERMINATOR, i, count, addrInit+i, query[2*i], query[2*i], query[2*i+1], query[2*i+1]);
    	#endif
	}

	return modbusNO_ERROR; // retorna sinalizando que a operação ocorreu bem
}

// -------------------------------------------------------------------------------------------------------------------
// Descrição: 	Função complementar do modbus slave para escrever no registrador
// Parametros:	Ver exemplos no modbus_Slave função modbus_SlaveAppendFunctions
// Retorna:		A função deve retornar
//					modbusNO_ERROR: Escrita feita com sucesso
//					modbusILLEGAL_DATA_ADDRESS:	Endereço do registrador inexistente
//					modbusILLEGAL_DATA_VALUE:	Valor para escrita é inválido
//					modbusSLAVE_DEVICE_FAILURE:	Um irrecuperável erro ocorreu enquanto o dispositivo estava tentando
//													executar a ação solicitada.
// -------------------------------------------------------------------------------------------------------------------
static int slave_WriteRegister(uint addr, u16 value) {
	int ret;

	#if (LOG_MODBUS_SLAVE == pdON)
    plognp("MODBUS: WRITE REG [a=0x%x] [val=0x%x]"CMD_TERMINATOR, addr, value);
    #endif

	ret = slave_SetValue(addr, value);
	if (ret == -1) ret = modbusILLEGAL_DATA_ADDRESS;
	else if (ret == -2) ret = modbusILLEGAL_DATA_VALUE;
	else if (ret == -3) ret = modbusSLAVE_DEVICE_FAILURE;
	else ret = modbusNO_ERROR; // retorna sinalizando que a operação ocorreu bem

    return ret;
}

// -------------------------------------------------------------------------------------------------------------------
// Descrição: 	Função complementar do modbus slave para escrever nos registradores
// Parametros:	Ver exemplos no modbus_Slave função modbus_SlaveAppendFunctions
// Retorna:		A função deve retornar
//					modbusNO_ERROR: Escrita feita com sucesso
//					modbusILLEGAL_DATA_ADDRESS:	Endereço do registrador inexistente
//					modbusILLEGAL_DATA_VALUE:	Valor para escrita é inválido
//					modbusSLAVE_DEVICE_FAILURE:	Um irrecuperável erro ocorreu enquanto o dispositivo estava tentando
//													executar a ação solicitada.
// -------------------------------------------------------------------------------------------------------------------
static int slave_WriteRegisters(uint addrInit, u8* query, uint count) {
	// checa se o endereço está dentro dos limite
	if ((count == 0) || (count > 120) ) return modbusILLEGAL_DATA_ADDRESS;

	// salva os valores recebidos nos seus respectivos registradores
	int ret;
	uint i; for (i=0;i<count;i++) {
		ret = slave_SetValue(addrInit+i, (((u8)query[2*i]<<8) | query[2*i+1]));
		if (ret == -1) return modbusILLEGAL_DATA_ADDRESS;
		else if (ret == -2) return modbusILLEGAL_DATA_VALUE;
		else if (ret == -3) return modbusSLAVE_DEVICE_FAILURE;

		#if (LOG_MODBUS_SLAVE == pdON)
    	plognp("MODBUS: [a=0x%x] [v=0x%x]"CMD_TERMINATOR, addrInit, (((u8)query[2*i]<<8) | query[2*i+1]));
    	#endif
	}

	return modbusNO_ERROR; // retorna sinalizando que a operação ocorreu bem
}


// ##################################################################################################
// MASTER
// ##################################################################################################
static int master_PutString(u8* buffer, u16 count) {
	rs485_ENTXM_ON = rs485_ENTXM;
	int ret = uartM_WriteTx(buffer, count);
	while (!uartM_EmptyTx()); // Vamos esperar que o serial envie a mensagem
	rs485_ENTXM_OFF = rs485_ENTXM;
	return ret;
}

static int waitResponse = pdFALSE;
static tCommand cmd;
static u16 regs[120]; // registrador de trabalho para troca de dados com os termômetros

// Para gravação os valores dos registradores devem já estar preenchidos
// Após uma leitura os registradores estarão atualizados

// Envia um comando para o escravo, nesta mesma função é feito o procedimento de erro de envio
// Uma vez enviado o comando com sucesso, capturar resposta no modbus_Process
//	BUSY: Ficar na espera da resposta
//  ERROR: Notificar um erro, e tomar o procedimento cabível
//  OK para escrita: Fazer nada, pois os valores dos registradores foram salvos no escravo com sucesso
//	OK para Leitura: Capturar os valores dos registradores lidos do escravo
// Parâmetros:
// 		slaveID. Endereço do escravo a ser acessado
// 		c: Tipo de comando para ser enviado ao escravo
typedef enum {readREGS, writeREG, writeREGS} tcmd;
void modbus_SendCommand(uint slaveID, tCommand c, u16 value) {
    if (waitResponse) return;

	cmd = c;
    // APONTA QUAIS REGISTRADORES A ACESSAR NO DISPOSITIVO
    // -----------------------------------------------------------------------------------------------------------------

    // Comando para ler os registradores: modelo, versão firmware e modo de operações dos reles
    tcmd typeCMD = readREGS;
    uint addrInit = 0;
    uint nRegs = 3;

    // Comando para ler os valores dos sensores
    if (cmd == cmdSET_TEMPERATURE_TYPE) {
        typeCMD = writeREG;
        addrInit = 0x20;
        nRegs = 1;
	}
	else if (cmd == cmdGET_TEMPERATURE) {
        addrInit = 0x200;
        nRegs = 4;
	}
	else if (cmd == cmdRST_TEMPERATURE_STATISTIC) {
        typeCMD = writeREG;
        addrInit = 0xA001;
        nRegs = 1;
	}


   	// ENVIA O COMANDO AO DISPOSITIVO ESCRAVO
   	// -----------------------------------------------------------------------------------------------------------------
   	int ret;
    if (typeCMD == writeREG) {
    	#if (LOG_MODBUS_MASTER == pdON)
    	plognp("modbus WriteReg [slave %d] [reg 0x%x] [value 0x%x]"CMD_TERMINATOR, slaveID, addrInit, value);
    	#endif
		ret =  modbus_MasterWriteRegister(slaveID, addrInit, value);
	} else if (typeCMD == writeREGS) {
	   	#if (LOG_MODBUS_MASTER == pdON)
    	plognp("modbus WriteRegs [slave %d] [reg 0x%x] [len %d]"CMD_TERMINATOR, slaveID, addrInit, nRegs);
    	#endif
        ret = modbus_MasterWriteRegisters(slaveID, addrInit, nRegs, regs);
    } else {
    	#if (LOG_MODBUS_MASTER == pdON)
    	plognp("modbus ReadRegs [slave %d] [reg 0x%x] [len %d]"CMD_TERMINATOR, slaveID, addrInit, nRegs);
    	#endif
		ret = modbus_MasterReadRegisters(slaveID, addrInit, nRegs, regs);
	}

	// se foi enviado com sucesso ficaremos na espera da resposta do termômetro
	if (ret == pdPASS) waitResponse = pdTRUE;

	#if (LOG_MODBUS_MASTER == pdON)
	else plognp("modbus err[%d] send querie"CMD_TERMINATOR, modbus_MasterReadStatus());
    #endif
}


void modbus_Tick(void) {
	modbus_MasterTick();
	modbus_SlaveTick();
}

static uint thermometerAccess = 1;
static uint triggerAction = 0;

void modbus_Process(void) {
	modbus_SlaveProcess();
	modbus_MasterProcess();

	// se não estamos esperando a resposta do modbus_SendCommand anterior vamos analisar o próximo comando a ser enviado
	if (!waitResponse) {
		tCommand cmdSend;
		u16 value = 0;
		int cmdConfig;
		if ( (control.setTempType) || (control.setTempStatics)) cmdConfig = pdTRUE; else cmdConfig = pdFALSE;

		// aponta para o próximo termômetro
		if (thermometerAccess >= nTHERMOMETER) { // se chegamos no último termômetro vamos apontar para o primeiro
			thermometerAccess = 1;

			// se foi algum comando de config dos termômetros vamos sinalizar que não tem mais configurações
			if ( cmdConfig ) {
				control.action = pdFALSE;
				triggerAction = pdFALSE;
				#if (LOG_APP == pdON)
				plognp("Finish config"CMD_TERMINATOR);
				#endif
			}
			control.setTempType = pdFALSE;
			control.setTempStatics = pdFALSE;
			cmdConfig = pdFALSE;
		} else thermometerAccess++;

		// verifica se foi emitido algum comando para configuração dos termômetros
		if ( (control.action == pdTRUE) && (triggerAction != control.action) && (cmdConfig) ) { // se é para fazer uma ação, faça
			thermometerAccess = 1; // aponta para o primeiro termômetro
			triggerAction = control.action;
			#if (LOG_APP == pdON)
			plognp("Init config"CMD_TERMINATOR);
			#endif
		}

		if (control.setTempType) {
			cmdSend = cmdSET_TEMPERATURE_TYPE;
			value = control.tempType;
		} else if (control.setTempStatics)
				cmdSend = cmdRST_TEMPERATURE_STATISTIC;
		else	cmdSend = cmdGET_TEMPERATURE;

		modbus_SendCommand(thermometerAccess, cmdSend, value);

		#if (LOG_APP == pdON)
		if ( cmdConfig ) plognp("Send Command to thermometer %d"CMD_TERMINATOR, thermometerAccess);
		#endif

		return;
	}

	int ret = modbus_MasterReadStatus();
	//	BUSY: Ficar na espera da resposta
	//  ERROR: Notificar um erro, e tomar o procedimento cabível
	//  OK para escrita: Fazer nada, pois os valores dos registradores foram salvos no escravo com sucesso
	//	OK para Leitura: Capturar os valores dos registradores lidos do escravo

	// se ainda está ocupado não faz nada
	if (ret == errMODBUS_BUSY) return;
	waitResponse = pdFALSE;
	// se for algum erro
	if (ret < 0) {
    	#if (LOG_MODBUS_MASTER == pdON)
    	plognp("modbus err[%d] wait response "CMD_TERMINATOR, ret);
		#endif
		if (cmd == cmdGET_TEMPERATURE) {
			if (ret == errMODBUS_EXCEPTION)
		 		control.thermometer[thermometerAccess-1].stsCom = modbus_MasterReadException();
					// modbusILLEGAL_FUNCTION: O termômetro recebeu uma função que não foi implementada ou não foi habilitada.
					// modbusILLEGAL_DATA_ADDRESS: O termômetro precisou acessar um endereço inexistente.
					// modbusILLEGAL_DATA_VALUE: O valor contido no campo de dado não é permitido pelo termômetro. Isto indica uma falta de informações na estrutura do campo de dados.
					// modbusSLAVE_DEVICE_FAILURE: Um irrecuperável erro ocorreu enquanto o termômetro estava tentando executar a ação solicitada.
			else control.thermometer[thermometerAccess-1].stsCom = 0; // sinaliza que não há comunicação com o termômetro
		}
		return;
	}

    // ATUALIZA VARS COM A LEITURA
    // -----------------------------------------------------------------------------------------------------------------

	#if (LOG_MODBUS_MASTER == pdON)
   	plognp("Response thermometer %d"CMD_TERMINATOR, thermometerAccess);
   	#endif

    // Comando para ler os registradores: modelo e versão firmware
    if (cmd == cmdGET_INFO) {
    	#if (LOG_MODBUS_MASTER == pdON)
    	plognp("model %c%c%c%c"CMD_TERMINATOR, (regs[0] & 0xff), (regs[0] >> 8), (regs[1] & 0xff), (regs[1] >> 8));
    	plognp("firmware %c.%c"CMD_TERMINATOR, (regs[2] & 0xff), (regs[2] >> 8));
    	#endif
    	control.thermometer[thermometerAccess-1].model[0] = (regs[0] & 0xff);
    	control.thermometer[thermometerAccess-1].model[1] = (regs[0] >> 8);
    	control.thermometer[thermometerAccess-1].model[2] = (regs[1] & 0xff);
    	control.thermometer[thermometerAccess-1].model[3] = (regs[1] >> 8);
    	control.thermometer[thermometerAccess-1].model[4] = 0;
    	control.thermometer[thermometerAccess-1].firmware[0] = (regs[2] & 0xff);
    	control.thermometer[thermometerAccess-1].firmware[1] = (regs[2] >> 8);
    	control.thermometer[thermometerAccess-1].firmware[2] = 0;

	// Comando para ler o termômetro
    } else if (cmd == cmdGET_TEMPERATURE) {
		control.thermometer[thermometerAccess-1].stsCom = 5; // sinaliza que a conexão fou feita com sucesso
		control.thermometer[thermometerAccess-1].sts =  regs[0] & 0x1f;
		control.thermometer[thermometerAccess-1].value = regs[1];
		control.thermometer[thermometerAccess-1].valueMin = regs[2];
		control.thermometer[thermometerAccess-1].valueMax = regs[3];
	}
}
